%% -*- coding: utf-8 -*-

%% @doc てんぷら抽象機械
%%
-module(templ_am).
-export([eval/1]).

%% @doc 式を評価する
%%
templel_eval(Val, Env) when is_function(Val) ->
	try Val(Env) of
		V -> {ok,V}
	catch
		error:{novalue,X} -> {novalue,X};
		error:What        ->
			throw({{?MODULE,einval},What,"eval of an expr failed"})
	end;
templel_eval(Val, _) ->
	{ok,Val}.


%% @spec eval(list(any())) -> string()
%% @doc てんぷら中間表現を評価する
%%
eval(P) ->
	eval(P, templ_env:restore_vtable(), halt_cont()).


% 命令リストの処理
eval([], _, Cont) ->
	apply(Cont, [{ok,[]}]);
eval([T|R], Env, Cont) ->
	eval(T, Env, eval_first_cont(R, Env, Cont));

% 値
eval({ok,Bin}, Env, Cont) when is_binary(Bin) ->
	eval({ok,binary_to_list(Bin)}, Env, Cont);
eval({Sig,Text}, _, Cont)
  when Sig =:= ok, is_list(Text); 
	   Sig =:= novalue ->
	apply(Cont, [{Sig,Text}]);
eval({expr,Val}, Env, Cont) ->
	{Sig,V} = templel_eval(Val, Env),
	eval({Sig,to_string(V)}, Env, Cont);

eval({'if',Cond,TBody,FBody}, Env, Cont) ->
	eval(Cond, Env, test_cont(TBody, FBody, Env, Cont));

eval({'let',{{name,_,Name},{expr,Val}},Body}, Env, Cont) ->
	case templel_eval(Val, Env) of
		{ok,V} ->
			NewEnv = templ_env:put_value(Env, Name, V),
			eval(Body, NewEnv, Cont);
		NoVal  ->
			throw({{?MODULE,einval},NoVal})
	end;

eval({foreach,{{name,_,Name},{expr,Vals}},Body}, Env, Cont) ->
	case templel_eval(Vals, Env) of
		{ok,[V|Vs]} ->
			eval([{foreach_first,{Name,V},Body,Vs}], Env, Cont);
		{ok,[]} ->
			eval([], Env, Cont);
		{ok,V} ->
			throw({{?MODULE,einval},V});
		{novalue,_} ->
			eval([], Env, Cont)
	end;
eval({foreach_first,{Name,Val},Body,Vals}, Env, Cont) ->
	NewEnv = templ_env:put_value(Env, Name, Val),
	templ_env:set_first(Name),
	case Vals of
		[] -> 
			templ_env:set_last(Name),
			eval(Body, NewEnv, foreach_last_cont(Name, Cont));
		_  -> 
			eval(Body, NewEnv, foreach_rest_cont(Name, Vals, Body, 1, Env, Cont))
	end;
eval({foreach_rest,Name,Val,Body,Vals,N}, Env, Cont) ->
	NewEnv = templ_env:put_value(Env, Name, Val),
	templ_env:unset_first(Name),
	templ_env:set_nth(Name, N),
	case Vals of
		[] -> 
			templ_env:set_last(Name),
			eval(Body, NewEnv, foreach_last_cont(Name, Cont));
		_  -> 
			eval(Body, NewEnv, foreach_rest_cont(Name, Vals, Body, N+1, Env, Cont))
	end;

eval(Arg, Env, Cont) ->
	throw({{?MODULE,einval},{Arg,Env,Cont},"bad state"}).


%% @doc 末端の継続
%%
halt_cont() ->
	fun (Val) -> 
			case Val of
				{ok,Text}   -> Text;
				{error,Why} -> erlang:error(Why)
			end
	end.

%% @doc 命令リストの最初の要素を処理
%%
eval_first_cont(Rest, Env, Cont) ->
	fun (Val) -> 
			case Val of
				{ok,Text}   -> eval(Rest, Env, eval_rest_cont(Text, Cont));
				Error       ->
					throw({{?MODULE,einval},Error})
			end
	end.

%% @doc 命令リストの残りを処理
%%
eval_rest_cont(FText, Cont) ->
	fun (Val) -> 
			case Val of
				{ok, Text}  -> apply(Cont, [{ok, FText++Text}]);
				Error       ->
					throw({{?MODULE,einval},Error})
			end
	end.


%% @doc if文の判定
%%
test_cont(TStmt, FStmt, Env, Cont) ->
	fun(Val) -> 
			case Val of
				{ok,Text}    -> 
					case Text of
						"false" -> eval(FStmt, Env, Cont);
						"0"     -> eval(FStmt, Env, Cont);
						""      -> eval(FStmt, Env, Cont);
						_       -> eval(TStmt, Env, Cont)
					end;
				{novalue,_X} -> 
					eval(FStmt, Env, Cont)
			end
	end.

%% @doc foreachの最初より後の処理
%%
foreach_rest_cont(Name, [V|Vs], Body, N, Env, Cont) ->
	fun(Val) -> 
			case Val of
				{ok, Text}   -> 
					templ_env:unset_first(Name),
					eval({foreach_rest,Name,V,Body,Vs,N}, Env, 
						 eval_rest_cont(Text, Cont));
				{error, _Why} -> none
			end
	end.
	
%% @doc foreachの最後
%%
foreach_last_cont(Name, Cont) ->
	fun(Val) -> 
			case Val of
				{ok, _Text}   -> 
					templ_env:unset_first(Name),
					templ_env:unset_last(Name),
					templ_env:unset_nth(Name);
					%templ_env:remove_value(Env, Name);
				{error, _Why} -> none
			end,
			apply(Cont, [Val])
	end.
	

%% @spec to_string(atom()|integer()|term()) -> list()
%% @doc 文字列に変換する
%%
to_string(Expr) when is_atom(Expr) ->
	atom_to_list(Expr);
to_string(Expr) when is_integer(Expr) ->
	integer_to_list(Expr);
to_string(Expr) ->
	Expr.
